// Tape.cs
//

using System;
using System.Collections.Generic;
using jQueryApi;
using System.Collections;

namespace TuringMachine
{
   public delegate void TapeCellEventHandler(TapeCell sender);

   public class TapeCell : Div
   {
      public event TapeCellEventHandler OnMouseEnter;
      public event TapeCellEventHandler OnMouseLeave;
      public event TapeCellEventHandler OnClick;

      private jQueryObject m_bitImage;

      private int m_character;
      static int counter = 0;
      string m_StyleUnhover = "tapewhite";


      public int pCharacter
      {
         get { return m_character; }
         set
         {
            m_character = value;
            m_bitImage.RemoveClass();
            if (m_character >= 0)
            {
               m_bitImage.AddClass("tapebit");
               m_bitImage.AddClass("tape_ch" + m_character);
            }
         }
      }

      public float pOpacity
      {
         get { return float.Parse(pObject.GetCSS("opacity")); }
         set { pObject.CSS("opacity", value.ToString()); }
      }

      public TapeCell(jQueryObject parent, string cellStyle) :
         base(parent, "tapecell")
      {
         m_StyleUnhover = cellStyle;
         counter++;
         string suffix = counter.ToString() + IdFactory.GetId();
         m_character = -1;

         pObject.AddClass("tapebit");
         pObject.AddClass(m_StyleUnhover);

         // add char
         string char_id = "char" + suffix;
         pObject.Append("<div id=\"" + char_id + "\"></div>");
         m_bitImage = jQuery.Select("#" + char_id);
         m_bitImage.CSS("top", "1px");
         m_bitImage.CSS("left", "1px");

         pObject.On("mouseenter", delegate(jQueryEvent ev)
         {
            if (OnMouseEnter != null)
               OnMouseEnter(this);
         });
         pObject.On("mouseleave", delegate(jQueryEvent ev)
         {
            if (OnMouseLeave != null)
               OnMouseLeave(this);
         });

         pObject.Click(delegate(jQueryEvent ev)
         {
            if (OnClick != null)
               OnClick(this);
         });

      }


      public void SetThickBorder(bool bActive)
      {
         if (bActive)
            pObject.AddClass("curractive");
         else
            pObject.RemoveClass("curractive");
      }

   }



   public class Tape
   {
      const int VISIBLE_CELL_COUNT = 20;
      const int CELL_DISTANCE = 25;
      const int DEFAULT_VALUE = 3;
      TapeCell[] cells;
      jQueryObject m_cellContainer;
      jQueryObject m_object;

      jQueryObject m_torch;
      jQueryObject m_courtainImage;
      Dictionary<int, int> TapeData = new Dictionary<int, int>();
      int m_iScrollPos = 0;
      private bool m_bReadOnly = false;
      StateBoard m_parent;
      bool m_bDebugMode = false;


      public Tape(StateBoard board, string cellStyle)
      {
         string id = "tape" + IdFactory.GetId();
         board.pParent.Append("<div id=\"" + id + "\"></div>");
         m_parent = board;
         m_object = jQuery.Select("#" + id);
         m_object.Height(32);
         m_object.Width((VISIBLE_CELL_COUNT + 2) * CELL_DISTANCE);
         m_object.CSS("position", "absolute");

         m_object.Append("<div></div>");
         m_cellContainer = m_object.Children();
         m_cellContainer.Height(32);
         m_cellContainer.CSS("left", CELL_DISTANCE + "px");
         m_cellContainer.Width(VISIBLE_CELL_COUNT * CELL_DISTANCE);
         m_cellContainer.CSS("position", "absolute");

         m_object.Append("<div id=\"t" + id + "\"></div>");
         m_torch = m_object.Children("#t" + id);
         m_torch.CSS("left", ((VISIBLE_CELL_COUNT / 2)) * CELL_DISTANCE + "px");
         m_torch.CSS("top", "-20px");
         m_torch.AddClass("tape_torch");
         m_torch.MouseDown(delegate(jQueryEvent ev)
         {
            HandleMouseDown(board.pParent, ev.ClientX, ev.Which);
         });


         m_object.Append("<img class=\"fadingborder\" src=\"fading.png\" />");
         m_courtainImage = m_object.Children(".fadingborder");
         m_courtainImage.Width(m_object.GetWidth());
         m_courtainImage.Height(m_object.GetHeight());
         m_courtainImage.CSS("border", "1px solid #808080");
         m_courtainImage.MouseDown(delegate(jQueryEvent ev)
         {
            HandleMouseDown(board.pParent, ev.ClientX, ev.Which);
         });

         m_courtainImage.Bind("contextmenu", delegate(jQueryEvent ev)
         {
            ev.PreventDefault();
         });


         cells = new TapeCell[VISIBLE_CELL_COUNT];
         for (int i = 0; i < VISIBLE_CELL_COUNT; i++)
         {
            cells[i] = new TapeCell(m_cellContainer, cellStyle);
            cells[i].pPosX = i * CELL_DISTANCE;
            cells[i].pPosY = 3;
            UpdateCell(i);
         }
      }

      private void HandleMouseDown(jQueryObject parent, int x, int which)
      {
         if (!m_bReadOnly)
         {
            int dir = which == 1 ? 1 : -1;
            HandleClick(x - parent.Position().Left, dir);
         }
      }

      public void Clear()
      {
         TapeData.Clear();
         Refresh();
      }

      public void Refresh()
      {
         for (int i = 0; i < VISIBLE_CELL_COUNT; i++)
            UpdateCell(i);
      }

      public void RemoveCell(int speed, EventHandler onTapeStop)
      {
         if (!m_bDebugMode || speed == 2)
         {
            Dictionary<int, int> newTapeData = new Dictionary<int, int>();
            int pointed = (int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos;
            foreach (int value in TapeData.Keys)
            {
               int iValue = int.Parse(value.ToString());
               if (iValue > pointed)
                  newTapeData[(int)iValue - 1] = TapeData[iValue];
               else if (iValue < pointed)
                  newTapeData[iValue] = TapeData[iValue];
            }
            TapeData = newTapeData;
            if (onTapeStop != null)
               onTapeStop(this, null);
         }
         else
         {
            int mid = (int)(VISIBLE_CELL_COUNT / 2) - 1;
            cells[mid].pObject.FadeOut(100, delegate
            {
               for (int i = mid + 1; i < VISIBLE_CELL_COUNT; i++)
               {
                  Dictionary dic = new Dictionary();
                  dic["left"] = cells[i].pPosX - CELL_DISTANCE;
                  cells[i].pObject.Animate(dic, 100, EffectEasing.Swing);
                  if (i == VISIBLE_CELL_COUNT - 1)
                  {
                     TapeCell midCell = cells[mid];
                     cells[mid].pPosX = cells[i].pPosX;
                     cells[mid].pVisible = true;
                     for (int j = mid; j < VISIBLE_CELL_COUNT - 1; j++)
                        cells[j] = cells[j + 1];
                     cells[VISIBLE_CELL_COUNT - 1] = midCell;
                     Dictionary<int, int> newTapeData = new Dictionary<int, int>();
                     int pointed = (int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos;
                     foreach (int value in TapeData.Keys)
                     {
                        int iValue = int.Parse(value.ToString());
                        if (iValue > pointed)
                           newTapeData[(int)iValue - 1] = TapeData[iValue];
                        else if (iValue < pointed)
                           newTapeData[iValue] = TapeData[iValue];
                     }
                     TapeData = newTapeData;
                     UpdateCell(VISIBLE_CELL_COUNT - 1);
                     Refresh();
                     if (onTapeStop != null)
                        onTapeStop(this, null);
                  }
               }
            });
         }
      }

      public void InsertCell(int speed, EventHandler onTapeStop)
      {
         if (!m_bDebugMode || speed == 2)
         {
            Dictionary<int, int> newTapeData = new Dictionary<int, int>();
            int pointed = (int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos;
            foreach (int value in TapeData.Keys)
            {
               int iValue = int.Parse(value.ToString());
               if (iValue >= pointed)
                  newTapeData[(int)iValue + 1] = TapeData[iValue];
               else
                  newTapeData[iValue] = TapeData[iValue];
            }
            TapeData = newTapeData;
            if (onTapeStop != null)
               onTapeStop(this, null);
         }
         else
         {
            int mid = (int)(VISIBLE_CELL_COUNT / 2) - 1;
            for (int i = mid; i < VISIBLE_CELL_COUNT; i++)
            {
               Dictionary dic = new Dictionary();
               dic["left"] = cells[i].pPosX + CELL_DISTANCE;
               Action cb = null;
               if (i == VISIBLE_CELL_COUNT - 1)
               {
                  cb = delegate
                  {
                     TapeCell lastCell = cells[VISIBLE_CELL_COUNT - 1];
                     lastCell.pVisible = false;
                     lastCell.pPosX = cells[mid].pPosX - CELL_DISTANCE;
                     lastCell.pObject.FadeIn(100, delegate()
                     {
                        if (onTapeStop != null)
                           onTapeStop(this, null);
                     });

                     for (int j = VISIBLE_CELL_COUNT - 1; j > mid; j--)
                        cells[j] = cells[j - 1];
                     cells[mid] = lastCell;
                     Dictionary<int, int> newTapeData = new Dictionary<int, int>();
                     int pointed = (int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos;
                     foreach (int value in TapeData.Keys)
                     {
                        int iValue = int.Parse(value.ToString());
                        if (iValue >= pointed)
                           newTapeData[(int)iValue + 1] = TapeData[iValue];
                        else
                           newTapeData[iValue] = TapeData[iValue];
                     }
                     TapeData = newTapeData;
                     UpdateCell(mid);

                  };
               }
               jQueryObject res = cells[i].pObject.Animate(dic, 100, EffectEasing.Swing, cb);
            }
         }
      }


      /// <summary>
      /// 
      /// </summary>
      /// <param name="dir"></param>
      /// <param name="speed">0: normal; 1:fast; 2: fastest</param>
      /// <param name="onTapeStop"></param>
      public void Move(int dir, int speed, EventHandler onTapeStop)
      {
         if (!m_bDebugMode || speed == 2)
         {
            m_iScrollPos += dir;
            if (onTapeStop != null)
               onTapeStop(this, null);
         }
         else
         {
            Dictionary dic = new Dictionary();
            Action onReady = null;
            if (dir == 1)
            {
               dic["left"] = 0;
               onReady = delegate
               {
                  TapeCell celllast = cells[0];
                  for (int i = 0; i < VISIBLE_CELL_COUNT - 1; i++)
                  {
                     cells[i] = cells[i + 1];
                  }
                  cells[VISIBLE_CELL_COUNT - 1] = celllast;
                  m_iScrollPos++;
                  for (int i = 0; i < VISIBLE_CELL_COUNT; i++)
                     cells[i].pPosX = i * CELL_DISTANCE;

                  m_cellContainer.CSS("left", CELL_DISTANCE + "px");
                  UpdateCell(VISIBLE_CELL_COUNT - 1);
                  if (onTapeStop != null)
                     onTapeStop(this, null);
               };
            }
            else
            {
               dic["left"] = CELL_DISTANCE * 2;
               onReady = delegate
               {
                  TapeCell cell0 = cells[VISIBLE_CELL_COUNT - 1];
                  for (int i = VISIBLE_CELL_COUNT - 1; i > 0; i--)
                  {
                     cells[i] = cells[i - 1];
                  }
                  cells[0] = cell0;
                  m_iScrollPos--;
                  for (int i = 0; i < VISIBLE_CELL_COUNT; i++)
                     cells[i].pPosX = i * CELL_DISTANCE;

                  m_cellContainer.CSS("left", CELL_DISTANCE + "px");
                  UpdateCell(0);
                  if (onTapeStop != null)
                     onTapeStop(this, null);
               };
            }
            switch (speed)
            {
               case 0: m_cellContainer.Animate(dic, EffectDuration.Fast, EffectEasing.Swing, onReady);
                  break;
               case 1: m_cellContainer.Animate(dic, 100, EffectEasing.Swing, onReady);
                  break;
               case 2:
                  m_cellContainer.CSS("left", ((int)dic["left"]) + "px");
                  onReady();
                  break;
            }
         }
      }


      private void HandleClick(int x, int dir)
      {
         int cellClicked = (int)(x / CELL_DISTANCE);
         cellClicked--;
         if (cellClicked >= 0 && cellClicked < VISIBLE_CELL_COUNT)
         {
            int cv = GetCellValue(cellClicked + m_iScrollPos);
            cv = (cv + dir + 4) % 4;
            if (!m_parent.m_abilities.pRedDotEnabled && cv == 2)
               cv += dir;
            SetCellValue(cellClicked + m_iScrollPos, cv);
         }
      }

      private void UpdateCell(int pos)
      {
         if (Script.IsNullOrUndefined(TapeData[(pos + m_iScrollPos)]))
            cells[pos].pCharacter = DEFAULT_VALUE;
         else
            cells[pos].pCharacter = TapeData[(pos + m_iScrollPos)];
      }

      public int pPosX
      {
         get { return m_object.Position().Left; }
         set
         {
            m_object.CSS("left", value + "px");
         }
      }
      public int pPosY
      {
         get { return m_object.Position().Top; }
         set
         {
            m_object.CSS("top", value + "px");
         }
      }

      public void SetTorchVisible(bool bVisible)
      {
         if (bVisible)
            m_torch.Show();
         else
            m_torch.Hide();
      }

      public void WriteValue(int v)
      {
         SetCellValue((int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos, v);
      }


      public string pTapeValue
      {
         set
         {
            m_iScrollPos = 0;
            int offset = (int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos;
            for (int i = 0; i < value.Length; i++)
            {
               string ch = value.Substr(i, 1);
               int ival = -1;
               switch (ch)
               {
                  case "0": ival = 0; break;
                  case "1": ival = 1; break;
                  case ".": ival = 2; break;
                  case "*": ival = 3; break;
               }
               if (ival != -1)
                  SetCellValue(i + offset, ival);
            }
         }


         get
         {
            m_iScrollPos = 0;
            int minValue = 10000000;
            foreach (int v in TapeData.Keys)
            {
               int iv = int.Parse(v.ToString());
               if (iv < minValue) minValue = iv;
            }

            string res = "";
            int pos = minValue;
            int ch;
            while ((ch = GetCellValue(pos)) != 3)
            {
               switch (ch)
               {
                  case 0: res += "0"; break;
                  case 1: res += "1"; break;
                  case 2: res += "."; break;
               }
               pos++;
            }
            return res;
         }
      }


      public int ReadValue()
      {
         return GetCellValue((int)(VISIBLE_CELL_COUNT / 2) - 1 + m_iScrollPos);
      }

      public bool pReadOnly
      {
         get { return m_bReadOnly; }
         set { m_bReadOnly = value; }
      }

      public int pCount
      {
         get
         {
            return TapeData.Count;
         }
      }

      private void SetCellValue(int pos, int v)
      {
         if (v != DEFAULT_VALUE)
            TapeData[pos] = v;
         else
            TapeData.Remove(pos);

         if (m_bDebugMode)
            UpdateCell(pos - m_iScrollPos);
      }

      public int GetCellValue(int pos)
      {
         if (Script.IsNullOrUndefined(TapeData[pos]))
            return DEFAULT_VALUE;
         else
            return TapeData[pos];
      }

      public jQueryObject pObject
      {
         get { return m_object; }
      }

      /// <summary>
      /// if true all changes are displayed in the ui
      /// </summary>
      public bool pDebugMode
      {
         get { return m_bDebugMode; }
         set { m_bDebugMode = value; }
      }
   }
}
